
import std.concurrency, std.stdio, std.math, std.typecons, std.conv, std.format, std.string, std.array;

import Definitions.msgCatalogue;
import Definitions.procNames;
import Registry.registry;

class Pan
{

    // =====================================================================
    // Constants
    //
    // Display width and height
    // This should be dynamic with layout manager when I figure how to do that
    immutable int X_WIDTH = 1009;
    immutable int Y_HEIGHT = 300;
    // Margins to allow for legends
    immutable int LEFT_MARGIN = 40;
    immutable int TOP_MARGIN = 20;

    // Number of cycles for averaging the display
    immutable int AV_CYCLES = 10;

    // Following should be configuration values
    // No of bins in the fft
    immutable int BINS = 4096;
    // Power low and high values in DBM
    immutable int HIGH_DB = -20;
    immutable int LOW_DB = -140;
    // Frequency span of the display
    immutable SAMPLE_RATE = 48000;
    immutable FREQ_SPAN = SAMPLE_RATE*2;
    // Fudge factor
    immutable double SCALE = 1.2;

    struct Point {
        int x;
        int y;
    }

    // =====================================================================
    // Variables
    //
    // Current cycle generated array of X points, also holds average after the cycle
    float [X_WIDTH] x_points;
    // Accumulates the average over AV_CYCLES cycles
    float [X_WIDTH][AV_CYCLES] x_average;
    auto ptr = 0;
    immutable auto MAX_PTR = AV_CYCLES-1;

    // The positive DBM range, e.g. -140 to -20 has a range of 120 DBM
    int dbm_range;
    // Holds the associative4 lookup table of DBM against Y coordinate
    int[int]lookup;

    // Data structures holding data to be rendered
    // These are shared because the update functions are callable from
    // any thread but the paint event runs on a different thread.
    // The render is synchronized to avoid conflicts.
    //
    // Panadapter
    // Of the form an array of Points to join up
    __gshared Point [X_WIDTH] pan_pts;
    __gshared int[X_WIDTH] mag_pts;
    // Horizontal grid
    // Of the form dbm(dbm start, line start, line end)
    __gshared Tuple!(Point, Point, Point)[string] h_grid;
    // Vertical grid
    // Of the form freq(freq start, line start, line end)
    __gshared Tuple!(Point, Point, Point)[string] v_grid;

    // Current center frequency
    float center_freq = 7.0;

    // CONSTRUCTOR
	this()
	{
		// Create a lookup list for the dbm to y coordinate
		createLookup();
		doOverlay();
	}

	public Tuple!(Point, Point, Point)[string] getPowerOverlay() {
	    return h_grid;
	}

	public Tuple!(Point, Point, Point)[string] getFreqOverlay() {
	    return v_grid;
	}

	public int[] getPlot() {
	    return mag_pts;
	}

    // PUBLIC
    public void data_update(immutable float [] data) {
        // Updates arrive at the update rate (usually 100ms)

        // Create an array of X,Y points for this update
        createPointsArray(data);
        makeFreqOverlay();

    }

    public void set_freq(double center_freq) {
        // (Re)set the center frequency

        this.center_freq = center_freq;
    }

    // EVENT HANDLERS
    /*
	protected override void onPaint(PaintEventArgs ea) {
		super.onPaint(ea);

        Pen p1 = new Pen(Color(0, 0xFF, 0));
        Pen p2 = new Pen(Color(0x40, 0x40, 0x40));
        // Draw the overlay
        foreach(legend, points; h_grid) {
            ea.graphics.drawText(legend, font, Color(38, 153, 84), Rect(points.field[0].x, points.field[0].y, LEFT_MARGIN, 10));
            ea.graphics.drawLine(p2, points.field[1].x,points.field[1].y, points.field[2].x, points.field[2].y);
        }
        foreach(legend, points; v_grid) {
            ea.graphics.drawText(legend, font, Color(38, 153, 84), Rect(points.field[0].x, points.field[0].y, points.field[0].x + 20, 10));
            ea.graphics.drawLine(p2, points.field[1].x,points.field[1].y, points.field[2].x, points.field[2].y);
        }
        // Draw the dynamic display
        ea.graphics.drawLines(p1, pan_pts);
	}
    */

	// PRIVATE
	private void createLookup() {

        // Create a lookup list for the DBM to Y coordinate
		// Create DB range
		if(HIGH_DB < 0) {
            dbm_range = abs(LOW_DB) - abs(HIGH_DB);
		}
		else {
            dbm_range = abs(LOW_DB) + HIGH_DB;
		}
		auto db_per_point = cast(float)(Y_HEIGHT - TOP_MARGIN)/cast(float)dbm_range;

		// Create the lookup
		// An associative array of key: DBM, value: Y coord
		auto i = 0;
		foreach(db; LOW_DB .. HIGH_DB) {
            lookup[db] = cast(int)(Y_HEIGHT - TOP_MARGIN - 1 - (i++*db_per_point));
		}
	}

	private void createPointsArray(immutable float [] data) {

        // Reduce the update to the width of the display
        float step = cast(float)BINS/cast(float)X_WIDTH;
        float av = 0.0;
        int inner_count = 0;
        foreach(i, ref r_dst; x_points) {
            av = 0.0;
            inner_count = 0;
            foreach(e_src; data[cast(int)(i*step) .. cast(int)((i*step)+step)]) {
                av = av + e_src;
                inner_count++;
            }
            av = av/inner_count;
            r_dst = av;
        }
        // x_points is now filled with the current snapshot
        // Add this into the circular averaging array
        x_average[ptr] = x_points;
        if (++ptr > MAX_PTR) {ptr = 0;}
        // Now average the results
        x_points[] = (x_average[0][]+x_average[1][]+x_average[2][]+x_average[3][]+x_average[4][]+x_average[5][]+x_average[6][]+x_average[7][]+x_average[8][]+x_average[9][])/10;
        // Fill the points array
        foreach(j, ref r_pts; pan_pts) {
            r_pts.x = j;
            auto fdbm = x_points[j];
            auto dbm = cast(int)(fdbm * SCALE);
            if(dbm > LOW_DB && dbm < HIGH_DB) {
                r_pts.y = lookup[dbm];
                mag_pts[j] = lookup[dbm];
            }
            else {
                mag_pts[j] = 0;
            }
        }
	}

    public void doOverlay() {
        // Redraw the static background

        makeFreqOverlay();
        makePowerOverlay();

    }

    private void makeFreqOverlay() {
        // Draw the vertical grid and frequency scale centered on this.center_freq
        // Make 9 ticks with the center being the current frequency
        // Whats a quick way to delete the array?
        auto keys = v_grid.keys;
        foreach(k; keys) {
            v_grid.remove(k);
        }
        auto no_of_divs = 9;
        auto freq_inc = cast(float)(FREQ_SPAN/9.0)/1000000.0;
        auto pix_per_freq_inc = (X_WIDTH-LEFT_MARGIN)/(no_of_divs-1);
        int offset;
        foreach(i; 0 .. 8) {
            // Create the vertical grid definition
            // Of the form freq(freq start, line start, line end)
            //__gshared Tuple!(Point, Point, Point)[string] v_grid;
            offset = (i*pix_per_freq_inc)+LEFT_MARGIN;
            auto writer = appender!string();
            formattedWrite(writer, "%#.4f", center_freq + ((i-4)*freq_inc));
            v_grid[writer.data] = tuple(Point(offset, 12), Point(offset, TOP_MARGIN), Point(offset, Y_HEIGHT));
        }
    }

	private void makePowerOverlay() {
        // Prepare the horizontal grid and power scale

        // Draw horizontal lines for the power
        auto no_of_divs = dbm_range/10;
        auto pix_per_ten_db = (Y_HEIGHT-TOP_MARGIN)/no_of_divs;
        int offset;
        foreach(i; 0 .. no_of_divs) {
            // Create the horizontal grid definition
            // Of the form legend(legend start, line start, line end)
            // __gshared Tuple!(Point, Point, Point)[string] h_grid;
            offset = (i*pix_per_ten_db)+TOP_MARGIN;
            h_grid[to!string(HIGH_DB - (i*10))] = tuple(Point(3,offset), Point(LEFT_MARGIN,offset), Point(X_WIDTH,offset));
        }
	}
}

